﻿// ===============================================================================
// EntityGenerator.cs
// EOS Framework
// ===============================================================================
// Copyright (c) EOS Development Group. All rights reserved.
// ===============================================================================

namespace EOS.Framework.Data.Generator
{
    using System;
    using System.CodeDom;
    using System.Globalization;
    using System.Reflection;
    using EOS.Framework.Tools;
using EOS.Framework.Tools.Aspects;

    /// <summary>
    /// Generator for entities based on database tables.
    /// </summary>
    public class EntityGenerator : BaseGenerator
    {
        /// <summary>
        /// Code definition for the entity class.
        /// </summary>
        private CodeTypeDeclaration classDeclaration;

        /// <summary>
        /// Initializes a new instance of the <see cref="EntityGenerator"/> class.
        /// </summary>
        /// <param name="targetNamespace">The target namespace.</param>
        /// <param name="className">Name of the class.</param>
        /// <param name="tableName">Name of the table for which the entity is created.</param>
        /// <exception cref="ArgumentNullException">
        /// 	<para><paramref name="targetNamespace"/> is null (Nothing in Visual Basic).</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="className"/> is null (Nothing in Visual Basic).</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="tableName"/> is null (Nothing in Visual Basic).</para>
        /// </exception>        
        /// <exception cref="ArgumentException">
        /// 	<para><paramref name="targetNamespace"/> is a string of length 0 or has only blanks.</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="className"/> is a string of length 0 or has only blanks.</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="tableName"/> is a string of length 0 or has only blanks.</para>
        /// </exception>
        [ParamNotNullOrEmpty("targetNamespace"), ParamNotNullOrEmpty("className"), ParamNotNullOrEmpty("tableName")]
        public EntityGenerator(string targetNamespace, string className, string tableName)
            : base(targetNamespace)
        {
            classDeclaration = new CodeTypeDeclaration(className);
            classDeclaration.IsClass = true;
            classDeclaration.IsPartial = true;
            classDeclaration.TypeAttributes = TypeAttributes.Public;
            classDeclaration.BaseTypes.Add(new CodeTypeReference("EOS.Framework.Data.BaseDomainObject"));

            classDeclaration.Comments.Add(new CodeCommentStatement("<summary>", true));
            classDeclaration.Comments.Add(new CodeCommentStatement(string.Format(CultureInfo.CurrentCulture, "Entity for the table '{0}'", tableName), true));
            classDeclaration.Comments.Add(new CodeCommentStatement("</summary>", true));

            TargetNamespace.Types.Add(classDeclaration);
        }

        /// <summary>
        /// Adds the database in reference.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="type">The type.</param>        
        /// <exception cref="ArgumentNullException">
        /// 	<para><paramref name="propertyName"/> is null (Nothing in Visual Basic).</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="fieldName"/> is null (Nothing in Visual Basic).</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="type"/> is null (Nothing in Visual Basic).</para>
        /// </exception>        
        /// <exception cref="ArgumentException">
        /// 	<para><paramref name="propertyName"/> is a string of length 0 or has only blanks.</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="fieldName"/> is a string of length 0 or has only blanks.</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="type"/> is a string of length 0 or has only blanks.</para>
        /// </exception>
        [ParamNotNullOrEmpty("propertyName"), ParamNotNullOrEmpty("fieldName"), ParamNotNullOrEmpty("type")]
        public void AddDatabaseInReference(string propertyName, string fieldName, string type)
        {
            AddDatabaseField(propertyName, fieldName, type);
        }

        /// <summary>
        /// Adds the database out reference.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="type">The type.</param>        
        /// <exception cref="ArgumentNullException">
        /// 	<para><paramref name="propertyName"/> is null (Nothing in Visual Basic).</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="fieldName"/> is null (Nothing in Visual Basic).</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="type"/> is null (Nothing in Visual Basic).</para>
        /// </exception>        
        /// <exception cref="ArgumentException">
        /// 	<para><paramref name="propertyName"/> is a string of length 0 or has only blanks.</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="fieldName"/> is a string of length 0 or has only blanks.</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="type"/> is a string of length 0 or has only blanks.</para>
        /// </exception>
        [ParamNotNullOrEmpty("propertyName"), ParamNotNullOrEmpty("fieldName"), ParamNotNullOrEmpty("type")]
        public void AddDatabaseOutReference(string propertyName, string fieldName, string type)
        {
            CodeMemberField field = new CodeMemberField(
                new CodeTypeReference("System.Collections.Generic.IList", 
                    new CodeTypeReference(type)), "_" + fieldName);

            field.Attributes = MemberAttributes.Private;

            field.Comments.Add(new CodeCommentStatement("<summary>", true));
            field.Comments.Add(new CodeCommentStatement(string.Format(CultureInfo.CurrentCulture, "Field for column '{0}'", fieldName), true));
            field.Comments.Add(new CodeCommentStatement("</summary>", true));

            classDeclaration.Members.Add(field);

            CodeMemberProperty property = new CodeMemberProperty();
            property.Name = propertyName;
            property.Attributes = MemberAttributes.Public;
            property.HasGet = true;
            property.HasSet = true;
            property.Type = new CodeTypeReference("System.Collections.Generic.IList",
                    new CodeTypeReference(type));

            property.GetStatements.Add(
                new CodeMethodReturnStatement(
                    new CodeFieldReferenceExpression(
                        new CodeThisReferenceExpression(), "_" + fieldName)));

            property.SetStatements.Add(
                new CodeAssignStatement(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "_" + fieldName),
                    new CodePropertySetValueReferenceExpression()));

            property.Comments.Add(new CodeCommentStatement("<summary>", true));
            property.Comments.Add(new CodeCommentStatement(string.Format(CultureInfo.CurrentCulture, "Gets or sets the {0} field", fieldName), true));
            property.Comments.Add(new CodeCommentStatement("</summary>", true));
            property.Comments.Add(new CodeCommentStatement(string.Format(CultureInfo.CurrentCulture, "<value>The {0} value.</value", fieldName), true));

            classDeclaration.Members.Add(property);
        }

        /// <summary>
        /// Adds the database field.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="type">The type.</param>        
        /// <exception cref="ArgumentNullException">
        /// 	<para><paramref name="propertyName"/> is null (Nothing in Visual Basic).</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="fieldName"/> is null (Nothing in Visual Basic).</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="type"/> is null (Nothing in Visual Basic).</para>
        /// </exception>        
        /// <exception cref="ArgumentException">
        /// 	<para><paramref name="propertyName"/> is a string of length 0 or has only blanks.</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="fieldName"/> is a string of length 0 or has only blanks.</para>
        /// 	<para>- or -</para>
        /// 	<para><paramref name="type"/> is a string of length 0 or has only blanks.</para>
        /// </exception>
        [ParamNotNullOrEmpty("propertyName"), ParamNotNullOrEmpty("fieldName"), ParamNotNullOrEmpty("type")]
        public void AddDatabaseField(string propertyName, string fieldName, string type)
        {
            CodeMemberField field = new CodeMemberField(new CodeTypeReference(type), "_" + fieldName);
            field.Attributes = MemberAttributes.Private;

            field.Comments.Add(new CodeCommentStatement("<summary>", true));
            field.Comments.Add(new CodeCommentStatement(string.Format(CultureInfo.CurrentCulture, "Field for column '{0}'", fieldName), true));
            field.Comments.Add(new CodeCommentStatement("</summary>", true));

            classDeclaration.Members.Add(field);

            CodeSnippetTypeMember method =
                new CodeSnippetTypeMember(string.Format("\t\tpartial void On{0}Changed({1} newValue);", propertyName, type));

            method.Comments.Add(new CodeCommentStatement("<summary>", true));
            method.Comments.Add(new CodeCommentStatement(string.Format(CultureInfo.CurrentCulture, "Called, when the '{0}' property is changed", propertyName), true));
            method.Comments.Add(new CodeCommentStatement("</summary>", true));
            method.Comments.Add(new CodeCommentStatement("<param name=\"newValue\">The new property value.</param>", true));

            classDeclaration.Members.Add(method);

            CodeMemberProperty property = new CodeMemberProperty();
            property.Name = propertyName;
            property.Attributes = MemberAttributes.Public;
            property.HasGet = true;
            property.HasSet = true;
            property.Type = new CodeTypeReference(type);

            property.GetStatements.Add(
                new CodeMethodReturnStatement(
                    new CodeFieldReferenceExpression(
                        new CodeThisReferenceExpression(), "_" + fieldName)));

            property.SetStatements.Add(
                new CodeConditionStatement(
                    new CodeBinaryOperatorExpression(
                        new CodePropertySetValueReferenceExpression(), CodeBinaryOperatorType.IdentityInequality,
                        new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "_" + fieldName)),
                        new CodeStatement[]
                        {   
                            new CodeExpressionStatement(
                                new CodeMethodInvokeExpression(
                                    new CodeMethodReferenceExpression(
                                        new CodeThisReferenceExpression(), "On" + propertyName + "Changed"),
                                    new CodePropertySetValueReferenceExpression())),

                            new CodeAssignStatement(
                                new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "_" + fieldName),
                                new CodePropertySetValueReferenceExpression()),

                            new CodeExpressionStatement(
                                new CodeMethodInvokeExpression(
                                    new CodeMethodReferenceExpression(
                                        new CodeThisReferenceExpression(), "NotifyPropertyChanged"),
                                    new CodePrimitiveExpression(propertyName),
                                    new CodePropertySetValueReferenceExpression()
                                    ))
                        }));

            property.Comments.Add(new CodeCommentStatement("<summary>", true));
            property.Comments.Add(new CodeCommentStatement(string.Format(CultureInfo.CurrentCulture, "Gets or sets the {0} field", fieldName), true));
            property.Comments.Add(new CodeCommentStatement("</summary>", true));
            property.Comments.Add(new CodeCommentStatement(string.Format(CultureInfo.CurrentCulture, "<value>The {0} value.</value", fieldName), true));

            classDeclaration.Members.Add(property);

        }
    }
}
